Here's another fun and confusing crash:
[adiumx.git] / Frameworks / Adium Framework / AIListOutlineView.m
blob27b5647c7d20b6cbd0a2ccc67e603842701f6d27
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "AIListCell.h"
18 #import "AIListOutlineView.h"
19 #import <AIUtilities/AIWindowAdditions.h>
20 #import <AIUtilities/CBApplicationAdditions.h>
21 #import <AIUtilities/ESOutlineViewAdditions.h>
23 #define MINIMUM_HEIGHT                          48
24 #define MINIMUM_WIDTH                           140
26 @interface AIListOutlineView (PRIVATE)
27 - (void)_initListOutlineView;
28 @end
30 @implementation AIListOutlineView
32 - (id)initWithCoder:(NSCoder *)aDecoder
34     [super initWithCoder:aDecoder];
35     [self _initListOutlineView];
36     return self;
39 - (id)initWithFrame:(NSRect)frame
41         [super initWithFrame:frame];
42         [self _initListOutlineView];
43         
44         return self;
47 - (void)_initListOutlineView
49         updateShadowsWhileDrawing = NO;
50         
51         backgroundImage = nil;
52         backgroundFade = 1.0;
53         backgroundColor = nil;
54         backgroundStyle = AINormalBackground;
55         
56         [self sizeLastColumnToFit];
59 - (void)dealloc
60 {       
61         [backgroundImage release];
62         [backgroundColor release];
63         
64         [super dealloc];
67 //Prevent the display of a focus ring around the contact list in 10.3 and greater
68 - (NSFocusRingType)focusRingType
70     return NSFocusRingTypeNone;
73 //When our delegate is set, ask it for our data cells
74 - (void)setDelegate:(id)delegate
76         [super setDelegate:delegate];
79 //Keep our column full width
80 - (void)setFrameSize:(NSSize)newSize
82         [super setFrameSize:newSize];
83         [self sizeLastColumnToFit];
87 //Selection Hiding -----------------------------------------------------------------------------------------------------
88 //If our window isn't in the foreground, we're not displaying a selection.  So override this method to pass NO for
89 //selected in that situation
90 - (void)_drawRowInRect:(NSRect)rect colored:(BOOL)colored selected:(BOOL)selected
92         if(![[self window] isKeyWindow]) selected = NO;
93         [super _drawRowInRect:rect colored:colored selected:selected];
95         
96 //When our view is inserted into a window, observe that window so we can hide selection when it's not main
97 - (void)configureSelectionHidingForNewSuperview:(NSView *)newSuperview
99     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidBecomeMainNotification object:[self window]];
100     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidResignMainNotification object:[self window]];
101     if([newSuperview window]){
102         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowBecameMain:) name:NSWindowDidBecomeMainNotification object:[newSuperview window]];
103         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowResignedMain:) name:NSWindowDidResignMainNotification object:[newSuperview window]];
104     }
107 //Redraw our cells so they can select or de-select
108 - (void)windowBecameMain:(NSNotification *)notification{
109         [self setNeedsDisplay:YES];
111 - (void)windowResignedMain:(NSNotification *)notification{
112         [self setNeedsDisplay:YES];
116 - (void)cancelOperation:(id)sender
118         [self deselectAll:nil];
121 //Sizing -----------------------------------------------------------------------------------------------------
122 // Returns our desired size
123 - (int)desiredHeight
125         int desiredHeight = [self totalHeight] + desiredHeightPadding;
126         return desiredHeight > MINIMUM_HEIGHT ? desiredHeight : MINIMUM_HEIGHT;
129 - (int)desiredWidth
131         unsigned        row;
132         unsigned        numberOfRows = [self numberOfRows];
133         int                     widestCell = 0;
134         id                      theDelegate = [self delegate];
135         
136         //Enumerate all rows, find the widest one
137         for(row = 0; row < numberOfRows; row++){
138                 id                      item = [self itemAtRow:row];
139                 NSCell          *cell = ([self isExpandable:item] ? groupCell : contentCell);
140         
141                 [theDelegate outlineView:self willDisplayCell:cell forTableColumn:nil item:item];
142                 int     width = [(AIListCell *)cell cellWidth];
143                 if(width > widestCell) widestCell = width;
144         }
145         
146         return ((widestCell > MINIMUM_WIDTH) || ignoreMinimumWidth) ? widestCell : MINIMUM_WIDTH;
149 - (void)setIgnoreMinimumWidth:(BOOL)inFlag
151         ignoreMinimumWidth = inFlag;
154 //Add padding to the desired height
155 - (void)setDesiredHeightPadding:(int)inPadding
157         desiredHeightPadding = inPadding;
161 //Background image ---------------------------------------------------------------
162 //Draw our background image or color with transparency
163 - (void)drawBackgroundInClipRect:(NSRect)clipRect
165         if([self drawsBackground]){
166                 //BG Color
167                 [[self backgroundColor] set];
168                 NSRectFill(clipRect);
169                 
170                 //Image
171                 NSScrollView    *enclosingScrollView = [self enclosingScrollView];
172                 if(backgroundImage && enclosingScrollView){
173                         NSRect  visRect = [enclosingScrollView documentVisibleRect];
174                         NSSize  imageSize = [backgroundImage size];
175                         NSRect  imageRect = NSMakeRect(0.0, 0.0, imageSize.width, imageSize.height);
177                         switch(backgroundStyle){
178                                 
179                                 case AINormalBackground:{
180                                         //Background image normal
181                                         [backgroundImage drawInRect:NSMakeRect(visRect.origin.x, visRect.origin.y, imageSize.width, imageSize.height)
182                                                                            fromRect:imageRect
183                                                                           operation:NSCompositeSourceOver
184                                                                            fraction:backgroundFade];
185                                         break;
186                                 }
187                                 case AIFillProportionatelyBackground:{
188                                         //Background image proportional stretch
189                                         
190                                         //Make the width change by the same proportion as the height will change
191                                         //visRect.size.width = imageSize.width * (visRect.size.height / imageSize.height);
192                                         
193                                         //Make the height change by the same proportion as the width will change
194                                         visRect.size.height = imageSize.height * (visRect.size.width / imageSize.width);
195                                         
196                                         //Background image stretch
197                                         [backgroundImage drawInRect:visRect
198                                                                            fromRect:imageRect
199                                                                           operation:NSCompositeSourceOver
200                                                                            fraction:backgroundFade];
201                                         break;
202                                 }
203                                 case AIFillStretchBackground:{
204                                         //Background image stretch
205                                         [backgroundImage drawInRect:visRect
206                                                                            fromRect:imageRect
207                                                                           operation:NSCompositeSourceOver
208                                                                            fraction:backgroundFade];
209                                         break;
210                                 }
211                                 case AITileBackground:{
212                                         //Tiling
213                                         NSPoint currentOrigin;
214                                         currentOrigin = visRect.origin;
216                                         //We'll repeat this vertical process as long as necessary
217                                         while (currentOrigin.y < (visRect.origin.y + visRect.size.height)) {
218                                                 //Reset the x axis to draw a series of images horizontally at this height
219                                                 currentOrigin.x = visRect.origin.x;
220                                                 
221                                                 //Draw as long as our origin is within the visible rect
222                                                 while (currentOrigin.x < (visRect.origin.x + visRect.size.width)) {
223                                                         //Draw at the current x and y at least once with the original size
224                                                         [backgroundImage drawInRect:NSMakeRect(currentOrigin.x, currentOrigin.y, imageSize.width, imageSize.height)
225                                                                                            fromRect:imageRect
226                                                                                           operation:NSCompositeSourceOver
227                                                                                            fraction:backgroundFade];
228                                                         
229                                                         //Shift right for the next iteration
230                                                         currentOrigin.x += imageSize.width;
231                                                 }
232                                                 
233                                                 //Shift down for the next series of horizontal draws
234                                                 currentOrigin.y += imageSize.height;
235                                         }
236                                         break;
237                                 }
238                         }
239                 }
240                 
241         }else{
242                 //If we aren't drawing a background, fill the rect with clearColor
243                 [[NSColor clearColor] set];
244                 NSRectFill(clipRect);
245         }
248 //Background -----------------------------------------------------------------
250 - (void)setBackgroundImage:(NSImage *)inImage
252         if(backgroundImage != inImage){
253                 [backgroundImage release];
254                 backgroundImage = [inImage retain];             
255                 [backgroundImage setFlipped:YES];
256         }
257         
258         [(NSClipView *)[self superview] setCopiesOnScroll:(!backgroundImage)];
259         [self setNeedsDisplay:YES];
262 - (void)setBackgroundStyle:(AIBackgroundStyle)inBackgroundStyle
264         backgroundStyle = inBackgroundStyle;
265         [self setNeedsDisplay:YES];
269 - (void)setBackgroundOpacity:(float)opacity forWindowStyle:(LIST_WINDOW_STYLE)windowStyle
271         backgroundOpacity = opacity;
273         //Reset all our opacity dependent values
274         [_backgroundColorWithOpacity release]; _backgroundColorWithOpacity = nil;
275         [_rowColorWithOpacity release]; _rowColorWithOpacity = nil;
276         
277         //Turn our shadow drawing hack on if they're going to be visible through the transparency
278         [self setUpdateShadowsWhileDrawing:((backgroundOpacity < 0.9) ||
279                                                                                 (windowStyle == WINDOW_STYLE_PILLOWS_FITTED))];
281         //Mockie and pillow lists always require a non-opaque window, other lists only require a non-opaque window when
282         //the user has requested transparency.
283         if(windowStyle == WINDOW_STYLE_MOCKIE || windowStyle == WINDOW_STYLE_PILLOWS || windowStyle == WINDOW_STYLE_PILLOWS_FITTED){
284                 [[self window] setOpaque:NO];
285         }else{
286                 [[self window] setOpaque:(backgroundOpacity == 1.0)];
287         }
289         [self setNeedsDisplay:YES];
292 - (void)setBackgroundFade:(float)fade
294         backgroundFade = fade;
295         [self setNeedsDisplay:YES];
297 - (float)backgroundFade
299         //Factor in opacity
300         return backgroundFade * backgroundOpacity;
303 //Background color (Opacity is added into the return automatically)
304 - (void)setBackgroundColor:(NSColor *)inColor
306         if(backgroundColor != inColor){
307                 [backgroundColor release];
308                 backgroundColor = [inColor retain];
309                 [_backgroundColorWithOpacity release];
310                 _backgroundColorWithOpacity = nil;
311         }
312         [self setNeedsDisplay:YES];
314 - (NSColor *)backgroundColor
316         //Factor in opacity
317         if(!_backgroundColorWithOpacity){ 
318                 _backgroundColorWithOpacity = [[backgroundColor colorWithAlphaComponent:backgroundOpacity] retain];
319         }
320         
321         return _backgroundColorWithOpacity;
324 //Alternating row color (Opacity is added into the return automatically)
325 - (void)setAlternatingRowColor:(NSColor *)color
327         if(rowColor != color){
328                 [rowColor release];
329                 rowColor = [color retain];
330                 [_rowColorWithOpacity release];
331                 _rowColorWithOpacity = nil;
332         }
333         
334         [self setNeedsDisplay:YES];
337 - (NSColor *)alternatingRowColor
339         if(!_rowColorWithOpacity){
340                 _rowColorWithOpacity = [[rowColor colorWithAlphaComponent:backgroundOpacity] retain];
341         }
342         
343         return _rowColorWithOpacity;
346 - (void)viewWillMoveToSuperview:(NSView *)newSuperview
348         [super viewWillMoveToSuperview:newSuperview];
349         
350         [(NSClipView *)newSuperview setCopiesOnScroll:(!backgroundImage)];
353 /*      ######################## Crappy code alert ########################
354  *      Drawing the background image/color should be as simple as subclassing
355  *      drawBackgroundInClipRect: but that method is only called in 10.3 and
356  *      we need 10.2 compatibility.
358  *      We need to get called after the background draws, but before the rows
359  *      start drawing.  A crappy solution is to draw our background right
360  *      before the outline view tries to draw its first row.  We only need to
361  *      do this when running in 10.2
362  */
363 - (void)drawRect:(NSRect)rect
364 {       
365         if(![NSApp isOnPantherOrBetter]) _drawBackground = YES;
366         [super drawRect:rect];
368         /*      #################### More Crappy Code ###################
369          *      This time for 10.3 compatibility.  10.3 does NOT invalidate the shadow
370          *      of a transparent window correctly, forcing us to do it manually each
371          *      time the window content is changed.  This is absolutely horrible for
372          *      performance, but the only way to avoid shadow ghosting in 10.3 :(
373          */
374         if(updateShadowsWhileDrawing) [[self window] invalidateShadow];
376 - (void)drawRow:(int)row clipRect:(NSRect)rect
378         if(_drawBackground){
379                 _drawBackground = NO;
380                 [self drawBackgroundInClipRect:[self frame]];
381         }
382         [super drawRow:row clipRect:rect];
384 - (void)setUpdateShadowsWhileDrawing:(BOOL)update{
385         updateShadowsWhileDrawing = update;
387 // ###################################################################
391 //Contact menu ---------------------------------------------------------------
392 //Return the selected object (to auto-configure the contact menu)
393 - (AIListObject *)listObject
395     int selectedRow = [self selectedRow];
397     if(selectedRow >= 0 && selectedRow < [self numberOfRows]){
398         return [self itemAtRow:selectedRow];
399     }else{
400         return nil;
401     }
404 - (NSArray *)arrayOfListObjects
406         return [self arrayOfSelectedItems];
409 @end